/* 
 * This file is part of Last.fm Now Playing Plugi-in for Adium
 * Copyright (c) 2011 Anton Bronnikov <anton.bronnikov@gmail.com>.
 * 
 * Last.fm Now Playing Plugi-in for Adium is free software: you can redistribute
 * it and/or modify it under the terms of the GNU General Public License as 
 * published by the Free Software Foundation, either version 3 of the License, 
 * or (at * your option) any later version.
 * 
 * Last.fm Now Playing Plugi-in for Adium is distributed in the hope that it 
 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General 
 * Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with 
 * Last.fm Now Playing Plugi-in for Adium. If not, see 
 * <http://www.gnu.org/licenses/>.
 *
 */

#import "LastFmNowPlaying.h"
#import <Adium/AIAccount.h>
#import <Adium/AIStatus.h>
#import <Adium/AIStatusControllerProtocol.h>
#import <Adium/AIMenuControllerProtocol.h>
#import <Adium/ESDebugAILog.h>

NSInteger  const LASTFM_STATUS_ID         = -8001;
NSString * const LASTFM_STATUS_ENABLED    = @"♩ Last.fm";
NSString * const LASTFM_STATUS_DISABLED   = @"♮ Last.fm";
NSString * const LASTFM_STATUS_NOWPLAYING = @"♩ %@";

NSString * const MENU_INSERTTOKEN = @"Insert Last.fm Token";

NSString * const TRIGGER_LASTFM_TRACKTITLE  = @"%_LastFmTrackTitle";
NSString * const TRIGGER_LASTFM_TRACKNAME   = @"%_LastFmTrackName";
NSString * const TRIGGER_LASTFM_TRACKARTIST = @"%_LastFmTrackArtist";
NSString * const TRIGGER_LASTFM_TRACKALBUM  = @"%_LastFmTrackAlbum";
NSString * const TRIGGER_LASTFM_TRACKURL    = @"%_LastFmTrackURL";

NSString * const PLUGIN_NAME        = @"Last.fm Now Playing";
NSString * const PLUGIN_AUTHOR      = @"Anton Bronnikov";
NSString * const PLUGIN_VERSION     = @"1.0";
NSString * const PLUGIN_URL         = @"http://last-fm-now-playing-for-adium.blogspot.com/";
NSString * const PLUGIN_DESCRIPTION = @"Last.fm radio currently playing track information";

NSString * const EMPTY_KEY_EQUIVALENT = @"";

#pragma mark -
#pragma mark Private interface : LastFmNowPlaying

@interface LastFmNowPlaying(private)

#pragma mark -
#pragma mark Methods

- (void) adjustStatusText;
- (void) refreshTags;

- (void) isertTriggerString:(id)sender;
- (NSMenuItem *)newMenuItemWithTitle:(NSString *)title action:(SEL)action representedObject:(id)representedObject itemTag:(NSInteger)itemTag;
- (NSMenu *)newTriggersMenu;
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem;

@end

#pragma mark -
#pragma mark Implementation : LastFmNowPlaying

@implementation LastFmNowPlaying

#pragma mark -
#pragma mark Property : preferences

- (LastFmNowPlayingPreferences *) preferences {  
  if (!_preferences) {
    _preferences = [[LastFmNowPlayingPreferences sharedInstance] retain];
    _preferences.delegate = self;
  }
  
  AILogWithSignature(@"preferences == %@", _preferences);

  return _preferences;
}

#pragma mark -
#pragma mark Property : observer

- (LastFmNowPlayingObserver *) observer {
  if (!_observer) {
    _observer = [[LastFmNowPlayingObserver alloc] initWithLastFmUserID:self.preferences.lastFmUserID state:self.preferences.pluginIsEnabled];
    _observer.delegate = self;
  }
  
  AILogWithSignature(@"observer == %@", _observer);

  return _observer;
}

#pragma mark -
#pragma mark User interface


#pragma mark -
#pragma mark LastFmNowPlayingObserverDelegate

- (void) playingStarted:(LastFmNowPlayingObserver *)sender {
  AILogWithSignature(@"Playback started");
  
  [self adjustStatusText];
  [self refreshTags];
}

- (void) playingStopped:(LastFmNowPlayingObserver *)sender {
  AILogWithSignature(@"Playback stopped");

  [self adjustStatusText];
  [self refreshTags];
}

- (void) playingNewTrack:(LastFmNowPlayingObserver *)sender {
  AILogWithSignature(@"Playing new track %@", sender.nowPlaying.title);
  
  [self adjustStatusText];
  [self refreshTags];
}

#pragma mark -
#pragma mark Protocol : LastFmNowPlayingPreferencesDelegate

- (void) changedLastFmUserID:(LastFmNowPlayingPreferences *)sender {
  AILogWithSignature(@"Last.fm user ID now is \'%@\'", sender.lastFmUserID);

  // Pass the change to Last.fm observer
  self.observer.lastFmUserID = sender.lastFmUserID;
}

- (void) changedPluginIsEnabled:(LastFmNowPlayingPreferences *)sender {
  AILogWithSignature(@"Plugin enable/disable state now is %i", sender.pluginIsEnabled);
  
  // Pass the change to Last.fm observer
  self.observer.isEnabled = sender.pluginIsEnabled;

  // Adjust the status text accordingly
  [self adjustStatusText];
}

- (BOOL) validateLastFmUserID:(NSString *)someLastFmUserID {
  AILogWithSignature(@"Validating the user ID \'%@\'", someLastFmUserID);

  // Validate user ID with Last.fm observer
  return [self.observer validateLastFmUserID:someLastFmUserID];
}

#pragma mark -
#pragma mark Adium integration

- (void) adjustStatusText {
  AILogWithSignature(@"Aligning status text with the plugin state");
  
  NSString * statusText = nil;
  
  if (self.preferences.pluginIsEnabled) {
    if (self.observer.isNowPlaying) {
      // Show current track if it is playing now
      statusText = [NSString stringWithFormat:LASTFM_STATUS_NOWPLAYING, self.observer.nowPlaying.title];
    } else {
      // Otherwise the standard text
      statusText = LASTFM_STATUS_ENABLED;
    }
  } else {
    statusText = LASTFM_STATUS_DISABLED;
  }
  
  [lastFmNowPlayingStatus setTitle:statusText];
  [adium.statusController savedStatusesChanged];
}

- (void) refreshTags {
  AILogWithSignature(@"Refreshing the tags");
  
  [[NSNotificationCenter defaultCenter] postNotificationName:Adium_RequestImmediateDynamicContentUpdate 
                                                      object:nil];
}

- (void) isertTriggerString:(id)sender {
  AILogWithSignature(@"Will try to insert \'%@\'", sender);
  
  id representedObject = [sender representedObject];
  
  if (representedObject && [representedObject isKindOfClass:[NSString class]]) {
    NSString * tagToInsert = (NSString *) representedObject;
    
    id responder = [[[NSApplication sharedApplication] keyWindow] firstResponder];
    
	if (responder && [responder isKindOfClass:[NSTextView class]]) {
      NSTextView * textView = (NSTextView *) responder;
      
      NSAttributedString * attributedString = [[NSAttributedString alloc] initWithString:tagToInsert 
                                                                              attributes:[textView typingAttributes]];
      
      if (attributedString && [attributedString length] > 0) {
        [textView insertText:attributedString];
      } else {
		NSBeep();
      }
     
      [attributedString release];
	}
  }
}

- (NSMenuItem *)newMenuItemWithTitle:(NSString *)title action:(SEL)action representedObject:(id)representedObject itemTag:(NSInteger)itemTag {
  AILogWithSignature(@"Creating new menu item \'%@\' (%i)", title, itemTag);
  
  NSMenuItem * menuItem = [[NSMenuItem alloc] initWithTitle:title action:action keyEquivalent:EMPTY_KEY_EQUIVALENT];
  
  [menuItem setTarget:self];
  [menuItem setTag:itemTag];
  [menuItem setRepresentedObject:representedObject];
  [menuItem setEnabled:YES];
  
  return menuItem;
}

- (NSMenu *)newTriggersMenu {
  AILogWithSignature(@"Creating new triggers menu");
                     
  NSMenu * triggersMenu = [[NSMenu alloc] init];

  // TRIGGER_LASTFM_TRACKTITLE
  [triggersMenu addItem:[[self newMenuItemWithTitle:TRIGGER_LASTFM_TRACKTITLE 
                                             action:@selector(isertTriggerString:)
                                  representedObject:TRIGGER_LASTFM_TRACKTITLE
                                            itemTag:0] autorelease]];
  // Separator
  [triggersMenu addItem:[NSMenuItem separatorItem]];
  
  // TRIGGER_LASTFM_TRACKNAME
  [triggersMenu addItem:[[self newMenuItemWithTitle:TRIGGER_LASTFM_TRACKNAME 
                                             action:@selector(isertTriggerString:)
                                  representedObject:TRIGGER_LASTFM_TRACKNAME
                                            itemTag:0] autorelease]];
  // TRIGGER_LASTFM_TRACKARTIST
  [triggersMenu addItem:[[self newMenuItemWithTitle:TRIGGER_LASTFM_TRACKARTIST 
                                             action:@selector(isertTriggerString:)
                                  representedObject:TRIGGER_LASTFM_TRACKARTIST
                                            itemTag:0] autorelease]];
  // TRIGGER_LASTFM_TRACKALBUM
  [triggersMenu addItem:[[self newMenuItemWithTitle:TRIGGER_LASTFM_TRACKALBUM 
                                             action:@selector(isertTriggerString:)
                                  representedObject:TRIGGER_LASTFM_TRACKALBUM
                                            itemTag:0] autorelease]];
  // TRIGGER_LASTFM_TRACKURL
  [triggersMenu addItem:[[self newMenuItemWithTitle:TRIGGER_LASTFM_TRACKURL 
                                             action:@selector(isertTriggerString:)
                                  representedObject:TRIGGER_LASTFM_TRACKURL
                                            itemTag:0] autorelease]];
  return triggersMenu;
}

- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
  AILogWithSignature(@"Validating menu item %@", menuItem);
  
  BOOL enable = NO;
  
  NSResponder * responder = [[[NSApplication sharedApplication] keyWindow] firstResponder];
  
  // Enabled only for text views in focus
  if (responder && [responder isKindOfClass:[NSTextView class]]) {
    // ... only if plugin is enabled
    if (self.preferences.pluginIsEnabled) {
      // ... and only if there is something playing right now
      if (self.observer.isNowPlaying) {
        enable = YES;
      }
    }
  }
  
  return enable;
}

#pragma mark -
#pragma mark Protocol : AIContentFilter

- (CGFloat) filterPriority {
  return DEFAULT_FILTER_PRIORITY;
}

- (NSAttributedString *) filterAttributedString:(NSAttributedString *)inAttributedString context:(id)context {
  AILogWithSignature(@"Will filter content \'%@\'", inAttributedString);

  NSAttributedString * outAttributedString = inAttributedString;

  if (self.preferences.pluginIsEnabled) {
    // Perform filtering
    NSRange range = {0};
    NSMutableAttributedString * filteredAttributedString = [inAttributedString mutableCopy];
    
    LastFmTrack * currentTrack = self.observer.nowPlaying;
    
    // TRIGGER_LASTFM_TRACKTITLE
    range = [[filteredAttributedString string] rangeOfString:TRIGGER_LASTFM_TRACKTITLE 
                                                     options:(NSLiteralSearch | NSCaseInsensitiveSearch)];
    if (range.location != NSNotFound) {
      [filteredAttributedString replaceCharactersInRange:range withString:currentTrack.title];
    }
    
    // TRIGGER_LASTFM_TRACKNAME
    range = [[filteredAttributedString string] rangeOfString:TRIGGER_LASTFM_TRACKNAME 
                                                     options:(NSLiteralSearch | NSCaseInsensitiveSearch)];
    if (range.location != NSNotFound) {
      [filteredAttributedString replaceCharactersInRange:range withString:currentTrack.name];
    }
    
    // TRIGGER_LASTFM_TRACKARTIST
    range = [[filteredAttributedString string] rangeOfString:TRIGGER_LASTFM_TRACKARTIST 
                                                     options:(NSLiteralSearch | NSCaseInsensitiveSearch)];
    if (range.location != NSNotFound) {
      [filteredAttributedString replaceCharactersInRange:range withString:currentTrack.artist];
    }

    // TRIGGER_LASTFM_TRACKALBUM
    range = [[filteredAttributedString string] rangeOfString:TRIGGER_LASTFM_TRACKALBUM 
                                                     options:(NSLiteralSearch | NSCaseInsensitiveSearch)];
    if (range.location != NSNotFound) {
      [filteredAttributedString replaceCharactersInRange:range withString:currentTrack.album];
    }

    // TRIGGER_LASTFM_TRACKURL
    range = [[filteredAttributedString string] rangeOfString:TRIGGER_LASTFM_TRACKURL 
                                                     options:(NSLiteralSearch | NSCaseInsensitiveSearch)];
    if (range.location != NSNotFound) {
      [filteredAttributedString replaceCharactersInRange:range withString:currentTrack.url];
    }
    
    outAttributedString = [[filteredAttributedString copy] autorelease];
    
    [filteredAttributedString release];
  }

  AILogWithSignature(@"Filtered result is \'%@\'", outAttributedString);
  
  return outAttributedString;
}

#pragma mark -
#pragma mark Protocol : AIPlugin

- (void) installPlugin {
  AILogWithSignature(@"Loading plugin");

  // Register self for content filtering  
  [adium.contentController registerContentFilter:self 
                                          ofType:AIFilterContent
                                       direction:AIFilterOutgoing];

  // Create a Last.fm Now Playing Status, assign with attributes and add to the controller
  lastFmNowPlayingStatus = [AIStatus statusOfType:AIAvailableStatusType];
  
  [lastFmNowPlayingStatus setTitle:pluginIsEnabled ? LASTFM_STATUS_ENABLED : LASTFM_STATUS_DISABLED];
  [lastFmNowPlayingStatus setUniqueStatusID:[NSNumber numberWithInteger:LASTFM_STATUS_ID]];
  [lastFmNowPlayingStatus setStatusMessage:[[[NSAttributedString alloc ] initWithString:TRIGGER_LASTFM_TRACKTITLE] autorelease]];
  [lastFmNowPlayingStatus setMutabilityType:AISecondaryLockedStatusState];
  [lastFmNowPlayingStatus setSpecialStatusType:AINowPlayingSpecialStatusType];
  
  [adium.statusController addStatusState:lastFmNowPlayingStatus];
  
  // Create "Edit" sub-menu for plugin triggers 
  NSMenuItem * editMenuItem = [[NSMenuItem alloc] initWithTitle:MENU_INSERTTOKEN action:NULL keyEquivalent:EMPTY_KEY_EQUIVALENT];
  NSMenu * editMenuTriggers = [self newTriggersMenu];
  [editMenuItem setSubmenu:editMenuTriggers];
  [adium.menuController addMenuItem:editMenuItem toLocation:LOC_Edit_Additions];
  [editMenuTriggers release];
  [editMenuItem release];
  
  // Create "Edit" sub-menu for plugin triggers 
  NSMenuItem * contextMenuItem = [[NSMenuItem alloc] initWithTitle:MENU_INSERTTOKEN action:NULL keyEquivalent:EMPTY_KEY_EQUIVALENT];
  NSMenu * contextMenuTriggers = [self newTriggersMenu];
  [contextMenuItem setSubmenu:contextMenuTriggers];
  [adium.menuController addContextualMenuItem:contextMenuItem toLocation:Context_TextView_Edit];
  [contextMenuTriggers release];
  [contextMenuItem release];
  
  // Update status text in line with current enabled state & playing track
  [self adjustStatusText];
}

- (void) uninstallPlugin {
  AILogWithSignature(@"Unloading plugin");

  // Stop content filtering
  [adium.contentController unregisterContentFilter:self];
}

#pragma mark -

- (NSString *) pluginAuthor {  
  return PLUGIN_AUTHOR;
}

- (NSString *) pluginVersion {
  return PLUGIN_VERSION;
}

- (NSString *) pluginDescription {
  return PLUGIN_DESCRIPTION;
}

- (NSString *) pluginURL {
  return PLUGIN_DESCRIPTION;
}

#pragma mark -
#pragma mark Object life-cycle

- (id) init {
  self = [super init];
  
  if (self) {
    pluginIsEnabled = NO;
    lastFmUserID = nil;
    lastFmNowPlayingStatus = nil;
    _preferences = nil;
    _observer = nil;
  }
  
  return self;
}

- (void) dealloc {
  if (lastFmUserID) {
    [lastFmUserID release];
    lastFmUserID = nil;
  }
  
  if (lastFmNowPlayingStatus) { 
    [lastFmNowPlayingStatus release];
    lastFmNowPlayingStatus = nil;
  }
  
  if (_preferences) { 
    _preferences.delegate = nil;
    [_preferences release];
    _preferences = nil;
  }
  
  if (_observer) {
    _observer.delegate = nil;
    [_observer release];
    _observer = nil;
  }
  
  [super dealloc];
}

@end
